//////////////////////////////////////////////////////////////////////////
//
// GAM203
// Sound Manager
// SoundMgr.cpp
// 
// Scott Taylor
// Qantm College
//
//////////////////////////////////////////////////////////////////////////

#include "SoundMgr.h"

#include "SoundSample.h"
#include "SoundEmitter.h"

#include <fmod.h>

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#pragma comment(lib, "fmodvc")

// Singleton instance
SoundMgr* Singleton<SoundMgr>::m_pInstance = NULL;

//
// Constructor
//
SoundMgr::SoundMgr()
{
}

//
// Destructor
//
SoundMgr::~SoundMgr()
{
	Shutdown();
}

//
// Initialisation
//
bool SoundMgr::Setup()
{
	// Attempt to use DirectSound as the underlying driver
	FSOUND_SetOutput(FSOUND_OUTPUT_DSOUND);

	// Debug info: List drivers
	int driverCount = FSOUND_GetNumDrivers();
	for (int i = 0; i < driverCount; ++i)
	{
		const char* driverName = FSOUND_GetDriverName(i);
		if (driverName)
		{
			unsigned int driverCapabilities;
			if (FSOUND_GetDriverCaps(i, &driverCapabilities))
			{
				char tempBuf[1024];
				sprintf_s(tempBuf, "SoundMgr found driver [%s], supports h/w [%s], raw caps value [%u]\n",
					driverName, (driverCapabilities & FSOUND_CAPS_HARDWARE) ? "Yes" : "No", driverCapabilities);
				OutputDebugString(tempBuf);
			}
		}
	}

	// Attempt to provide software support for at least a small number of hardware channels
	FSOUND_SetMinHardwareChannels(16);

	// Initialise FMOD
	if (!FSOUND_Init(44100, 32, 0))
	{
		// Failed to init FMOD
		return false;
	}

	return true;
}

//
// Shutdown
//
void SoundMgr::Shutdown()
{
	// Delete all known emitters
	for (SoundEmitterArray::iterator iter = m_emitters.begin(); iter != m_emitters.end(); ++iter)
	{
		SoundEmitter* pElem = *iter;
		delete pElem;
	}
	m_emitters.clear();

	// Free all loaded samples
	for (SoundSampleArray::iterator iter = m_samples.begin(); iter != m_samples.end(); ++iter)
	{
		SoundSample* pElem = *iter;
		delete pElem;
	}
	m_samples.clear();

	FSOUND_Close();
}

//
// Per-frame update
//
void SoundMgr::Update()
{
	for (SoundEmitterArray::iterator iter = m_emitters.begin(); iter != m_emitters.end(); /* No iter increment - see below */)
	{
		SoundEmitter* pElem = *iter;

		// Update the sound emitter
		pElem->Update();

		// Check if it needs to be destroyed
		if (pElem && pElem->IsDestroyOnStop() && !pElem->IsPlaying())
		{
			// This emitter has finished its sound and should be destroyed
			delete pElem;
			iter = m_emitters.erase(iter);  // Moves to next element
		}
		else
		{
			// Not removing this emitter, move to next
			++iter;
		}
	}

	// Update FMOD
	FSOUND_Update();
}

//
// Updates listener position
// This should be called once per frame (or whenever the camera moves)
//
void SoundMgr::SetListener(float a_xPos, float a_yPos, float a_zPos, float a_xDir, float a_yDir, float a_zDir)
{
	m_listenerPos.x = a_xPos;
	m_listenerPos.y = a_yPos;
	m_listenerPos.z = a_zPos;
	m_listenerDir.x = a_xDir;
	m_listenerDir.y = a_yDir;
	m_listenerDir.z = a_zDir;

	// Assume Y-up = +1.0f and there is no camera roll, ignore velocity (for doppler only)
	FSOUND_3D_Listener_SetAttributes((float*)&m_listenerPos, NULL, a_xDir, a_yDir, a_zDir, 0, 1.0f, 0);

	// Set attenuation on sounds as a factor of real world (1.0 = real world, 2.0 = twice as fast falloff, etc)
	FSOUND_3D_SetRolloffFactor(0.5f);
}

//
// Creates a new sound emitter
// Searches for an existing loaded sample with the given filename
// Returns a pointer to the emitter object, or NULL if not found
//
SoundEmitter* SoundMgr::CreateEmitter(const char* a_pFilename, bool a_is3d)
{
	// Attempt to find an existing sample
	SoundSample* pSample = FindSampleInternal(a_pFilename);

	if (!pSample)
	{
		// Sample was not already loaded, load it from file
		pSample = LoadSampleInternal(a_pFilename, a_is3d);
	}

	if (pSample)
	{
		// Sample was found or loaded, create an emitter
		return CreateEmitterInternal(pSample);
	}

	return NULL;
}

//
// Creates a new sound emitter based on an existing one
// Returns a pointer to the emitter object, or NULL if failed
//
SoundEmitter* SoundMgr::CreateEmitter(SoundEmitter* a_pCopyEmitter)
{
	if (a_pCopyEmitter)
	{
		return CreateEmitterInternal(a_pCopyEmitter->GetSample());
	}
	return NULL;
}

//
// Loads a sample from file
// Returns a pointer to the sample object, or NULL if failed
//
SoundSample* SoundMgr::LoadSampleInternal(const char* a_pFilename, bool a_is3d)
{
	if (!a_pFilename)
	{
		return NULL;
	}

	SoundSample* pNewSound = new SoundSample(a_pFilename, a_is3d);
	if (pNewSound)
	{
		if (pNewSound->IsValid())
		{
			m_samples.push_back(pNewSound);
			return pNewSound;
		}

		// Failed to load sound or set up sample; clean up memory we allocated
		delete pNewSound;
	}
	return NULL;
}

//
// Looks for a loaded sample
// Returns a pointer to the sample object, or NULL if not found
//
SoundSample* SoundMgr::FindSampleInternal(const char* a_pFilename)
{
	if (!a_pFilename)
	{
		return NULL;
	}

	// Look for this filename within the loaded samples
	for (SoundSampleArray::iterator iter = m_samples.begin(); iter != m_samples.end(); ++iter)
	{
		SoundSample* pElem = *iter;
		// NOTE: This is a SLOW (DODGY!) string compare
		//       Ideally we should be using a string hash instead
		if (strcmp(a_pFilename, pElem->GetFilename()) == 0)
		{
			return pElem;
		}
	}

	return NULL;  // Not found
}

//
// Creates a new sound emitter
// Returns a pointer to the emitter object, or NULL if failed
//
SoundEmitter* SoundMgr::CreateEmitterInternal(SoundSample* a_pSample)
{
	if (a_pSample)
	{
		SoundEmitter* pNewEmitter = new SoundEmitter(a_pSample);
		if (pNewEmitter)
		{
			m_emitters.push_back(pNewEmitter);
			return pNewEmitter;
		}
	}
	return NULL;
}

//
// Gets sound engine CPU usage
// Returns a floating point value from 0..1
//
float SoundMgr::GetCPUUsage() const
{
	// Convert FMOD's CPU usage from 0..100 to 0..1
	return FSOUND_GetCPUUsage() / 100.0f;
}

bool SoundMgr::PreloadSample(const char *a_pFileName, bool a_is3d)
{
	// Attempt to find an existing sample
	SoundSample* pSample = FindSampleInternal(a_pFileName);

	if(!pSample)
	{
		//Sample not already loaded, load it from file
		pSample = LoadSampleInternal(a_pFileName, a_is3d);
		return true;	
	}
	//Already loaded
	return false;
}